unit Core_SettingsEngine;

interface
uses
  Windows, Core_Types;

type
  PDataKnot = ^TDataKnot;
  
  TDataKnot = packed record
    Name : array[0..63] of Char;
    Parent, FirstChild, Next : PDataKnot;
    ChildCount : Cardinal;
    DataLength : Cardinal;
    Data : Pointer;
  end;

var
  ConfigRoot : PDataKnot;

procedure LoadConfig;
procedure SaveConfig;
procedure Core_SaveSetting(Path : PChar; DataLength : Integer; Data : Pointer); stdcall;
function Core_Settings_CreateNode(Path : String):PDataKnot;
function Core_Settings_CreateKnot(Parent : PDataKnot; Name : String):PDataKnot;

function Core_Settings_GetNodeByPath(Path : String):PDataKnot;

function Core_Settings_GetStringValue(Path : PChar; Default : PChar = nil):PChar;
function Core_Settings_GetBooleanValue(Path : PChar; Default : Boolean = False):Boolean;
function Core_Settings_GetIntegerValue(Path : PChar; Default : Integer = 0):Integer;

implementation

uses SysUtils;

var
  ConfigData : Pointer;

function Core_Settings_GetBooleanValue(Path : PChar; Default : Boolean = False):Boolean;
var
  pNode : PDataKnot;
begin
  pNode := Core_Settings_GetNodeByPath(Path);

  if pNode = nil then
    Result := Default else
      Result := Boolean(pNode^.Data^);
end;

function Core_Settings_GetIntegerValue(Path : PChar; Default : Integer = 0):Integer;
var
  pNode : PDataKnot;
begin
  pNode := Core_Settings_GetNodeByPath(Path);

  if pNode = nil then
    Result := Default else
      Result := Integer(pNode^.Data^);
end;

function Core_Settings_GetStringValue(Path : PChar; Default : PChar = nil):PChar;
var
  pNode : PDataKnot;
begin
  pNode := Core_Settings_GetNodeByPath(Path);
  if pNode = nil then
    Result := Default else
      Result := PChar(pNode^.Data);
end;

function CountTotalData(Knot : PDataKnot):Cardinal;
begin
  if Knot <> nil then
  begin
    Result := Knot^.DataLength;
    if Knot^.FirstChild <> nil then
      Result := Result + CountTotalData(Knot^.FirstChild);

    if Knot^.Next <> nil then
    begin
      Result := Result + CountTotalData(Knot^.Next);
    end;

    Result := Result + Sizeof(TDataKnot);
  end else Result := 0;
end;

function WriteMemoryData(pData : Pointer; Knot : PDataKnot):Pointer;
var
  pNode : PDataKnot;
begin
  pNode := Knot;
  while pNode <> nil do
  begin

    CopyMemory(pData, pNode, Sizeof(TDataKnot));
    pData := Pointer(Integer(pData) + Sizeof(TDataKnot));
    CopyMemory(pData, pNode^.Data, pNode^.DataLength);
    pData := Pointer(DWord(pData) + pNode^.DataLength);

    if pNode^.FirstChild <> nil then
      pData := WriteMemoryData(pData, pNode^.FirstChild);

    pNode := pNode^.Next;
  end;

  Result := pData;
end;

procedure SaveConfig;
var
  ByteCount : Cardinal;
  Config : File of Byte;
  NewData : Pointer;
begin
  ByteCount := CountTotalData(ConfigRoot);
  GetMem(NewData, ByteCount);
  FillChar(NewData^, ByteCount, 0);

  WriteMemoryData(NewData, ConfigRoot);

  AssignFile(Config, 'config.dat');
  Rewrite(Config);

  BlockWrite(Config, NewData^, ByteCount);

  CloseFile(Config);

end;

function Core_Settings_CreateKnot(Parent : PDataKnot; Name : String):PDataKnot;
begin
  new(Result);

  FillChar(Result^, SizeOf(TDataKnot), 0);

  StrCopy(Result^.Name, @Name[1]);
  Result^.Parent := Parent;
  Result^.FirstChild := nil;
  Result^.Next := nil;
  Result^.ChildCount := 0;
  Result^.DataLength := 0;
  Result^.Data := nil;

  if Parent <> nil then
    Inc(Parent^.ChildCount);
end;

function Core_Settings_CreateNode(Path : String):PDataKnot;
var
  NodeNames : array of String;
  i : Integer;
  pNode : PDataKnot;
begin

  Result := nil;

  if Pos('/', Path) <> 0 then
  begin
    while Pos('/', Path) <> 0 do
    begin
      SetLength(NodeNames, Length(NodeNames)+1);
      NodeNames[High(NodeNames)] := Copy(Path, 1, Pos('/', Path)-1);
      Delete(Path, 1, Pos('/', Path));
    end;
    if Path <> '' then
    begin
      SetLength(NodeNames, Length(NodeNames)+1);
      NodeNames[High(NodeNames)] := Path;
    end;
  end else
  begin
    SetLength(NodeNames, 1);
    NodeNames[0] := Path;
  end;

  pNode := ConfigRoot;
  for i := 0 to High(NodeNames) do
  begin
    if pNode = nil then
    begin
      pNode := Core_Settings_CreateKnot(nil, NodeNames[i]);
    end;

    while pNode <> nil do
    begin
      if pNode.Name = NodeNames[i] then
      begin
        if i = High(NodeNames) then
        begin
          Result := pNode;
          Exit;
        end else
        begin
          if pNode^.FirstChild = nil then
          begin
            pNode^.FirstChild := Core_Settings_CreateKnot(pNode, NodeNames[i+1]);
            pNode := pNode^.FirstChild;
            Break;
          end else
          begin
            pNode := pNode.FirstChild;
            Break;
          end;
        end;
      end else
      begin
        if pNode^.Next = nil then
        begin
          pNode^.Next := Core_Settings_CreateKnot(pNode.Parent, NodeNames[i]);
          pNode := pNode^.Next;
        end else
          pNode := pNode^.Next;
      end;
    end;
  end;

end;

function Core_Settings_GetNodeByPath(Path : String):PDataKnot;
var
  NodeNames : array of String;
  i : Integer;
  pNode : PDataKnot;
begin
  Result := nil;
  if Pos('/', Path) <> 0 then
  begin
    while Pos('/', Path) <> 0 do
    begin
      SetLength(NodeNames, Length(NodeNames)+1);
      NodeNames[High(NodeNames)] := Copy(Path, 1, Pos('/', Path)-1);
      Delete(Path, 1, Pos('/', Path));
    end;
    if Path <> '' then
    begin
      SetLength(NodeNames, Length(NodeNames)+1);
      NodeNames[High(NodeNames)] := Path;
    end;
  end else
  begin
    SetLength(NodeNames, 1);
    NodeNames[0] := Path;
  end;

  pNode := ConfigRoot;
  for i := 0 to High(NodeNames) do
  begin
    while pNode <> nil do
    begin
      if pNode^.Name = NodeNames[i] then
      begin
        if i = High(NodeNames) then
          Result := pNode;
          
        pNode := pNode^.FirstChild;
        Break;
      end;
      if pNode^.Next <> nil then
        pNode := pNode^.Next else
        begin
          Result := nil;
          Exit;
        end;
    end;
  end;
end;

procedure Core_SaveSetting(Path : PChar; DataLength : Integer; Data : Pointer); stdcall;
var
  pNode : PDataKnot;
begin
  pNode := Core_Settings_GetNodeByPath(Path);

  if pNode = nil then
    pNode := Core_Settings_CreateNode(Path);
    
  pNode^.DataLength := DataLength;
  pNode^.Data := GetMemory(DataLength);
  CopyMemory(pNode^.Data, Data, DataLength);
end;

function AssignKnot(pData, pParent:Pointer):PDataKnot;
begin

  Result := pData;
  Result^.Parent := pParent;
  if Result^.DataLength <> 0 then
  begin
    Result^.Data := Pointer(DWord(pData) + SizeOf(TDataKnot));
  end else
    Result^.Data := nil;

  pData := Pointer(DWord(pData) + SizeOf(TDataKnot) + Result^.DataLength);

  if Result^.ChildCount > 0 then
  begin
    Result^.FirstChild := pData;
    pData := AssignKnot(pData, Result);
  end;

  if Result^.Next <> nil then
  begin
    Result^.Next := pData;
    pData := AssignKnot(pData, Result^.Parent);
  end;
  Result := pData;
end;

procedure ParceConfig;
begin
  ConfigRoot := ConfigData;
  AssignKnot(ConfigData, nil);
end;

procedure LoadConfig;
var
  Cfg : File of Byte;
begin
  if FileExists('config.dat') then
  begin
    AssignFile(Cfg, 'config.dat');
    Reset(Cfg);

    GetMem(ConfigData, FileSize(Cfg));
    BlockRead(Cfg, ConfigData^, FileSize(Cfg));

    ParceConfig;

    CloseFile(Cfg);
  end else ConfigRoot := Core_Settings_CreateNode('Common');
end;

initialization
  LoadConfig;

end.
